<?php

namespace App\Supports\Auth\Wechat;

use App\Models\User;
use EasyWeChat\Factory;
use App\Events\NewUserRegisteredEvent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\ValidationException;

class WeminiLogger
{
	protected $mini;
	
	protected $code = null;
	
	protected $authData;
	protected $iv = null;
	protected $encryptedData = null;
	
	protected $unionid;
	protected $openid;
	protected $sessionkey;
	
	protected $phoneNumber = null;
	
	protected $password = null;
	
	protected $inviteCode;
	
	protected $inviteString;
	protected $inviter;
	
	public function __construct()
	{
		$route = config('wxpay.default');
		$items = config("wxpay.{$route}");
		
		$config = [
			'app_id'  => $items['miniapp_id'],
			'secret'  => $items['miniapp_sc'],
		];
		
		$this->mini = Factory::miniProgram($config);
	}
	
	protected function phoneValidate()
	{
		request()->validate([
			'code' => 'required|string',
			'iv' => 'nullable|string',
			'encrypted_data' => 'nullable|string'
		]);
		
		$this->code = request('code');
		
		try {
			$authData = $this->mini->auth->session($this->code);
			if (isset($authData['errcode'])) {
				// CODE参数错误
				throw ValidationException::withMessages(['code' => '请搜索登录省钱小卖部极速版']);
			}
			$this->setParams($authData);
		} catch (Exception $e) {
			throw ValidationException::withMessages(['code' => '参数错误，无法获取用户信息']);
		}
	}
	
	protected function loginValidate()
	{
		request()->validate([
			'phone' => 'required|phone:CN,mobile',
			'unionid' => 'required|string|unique:users,wx_unionid',
			'openid' => 'required|string|unique:users,wm_openid',
			'password' => 'required|string|min:6',
			'invite_string' => 'nullable|string',
			'invite_code' => 'nullable|string',
		]);
		$this->phoneNumber = request('phone', null);
		$this->password = bcrypt(request('password'));
		$this->unionid = request('unionid', null);
		$this->openid = request('openid', null);
		$this->inviteCode = request('invite_code', null);
		$this->inviteString = request('invite_string', null);
		$this->setInviter();
	}
	
	protected function setParams(array $authData)
	{
		$this->authData = $authData;
		$this->iv = request('iv', null);
		$this->encryptedData = request('encrypted_data', null);
		$this->setAuthData();
		$this->setPhoneNumber();
	}
	
	protected function setAuthData()
	{
		$data = $this->authData;
		$this->unionid = isset($data['unionid']) ? $data['unionid'] : null;
		$this->openid = isset($data['openid']) ? $data['openid'] : null;
		$this->sessionkey = isset($data['session_key']) ? $data['session_key'] : null;
	}
	
	protected function setPhoneNumber()
	{
		$phoneNumber = null;
		if ($this->iv and $this->encryptedData) {
			$sessionData = $this->phoneFromSessionKey();
			$phoneNumber = isset($sessionData['purePhoneNumber']) ? $sessionData['purePhoneNumber'] : null;
		}
		$this->phoneNumber = $phoneNumber;
	}
	
	protected function setInviter()
	{
		$inviter = null;
		if ($this->inviteString) {
			$inviter = inviter()->base()->inviterFromString($this->inviteString);
		}
		if ($this->inviteCode) {
			$inviter = inviter()->base()->inviterFromCode($this->inviteCode);
		}
		$this->inviter = $inviter ?? null;
	}
	
	protected function phoneFromSessionKey() : array
	{
		try {
			$sessionKey = $this->sessionkey;
			return $this->mini->encryptor->decryptData($sessionKey, $this->iv, $this->encryptedData);
		} catch (\Exception $exception) {
			return [];
		}
	}
	
	protected function userFromPhone()
	{
		if ($this->phoneNumber) {
			return User::where('phone', $this->phoneNumber)->first();
		}
		return null;
	}
	
	protected function userFromPhoneAndOpenId()
	{
		if ($this->phoneNumber) {
			$user = User::where('phone', $this->phoneNumber)
				->whereNotNull('wx_unionid')
				->where(function (Builder $query) {
					$query->whereNotNull('wm_openid')->orWhereNotNull('wx_openid');
				})->first();
			return $user;
		}
		return null;
	}
	
	protected function authNew()
	{
		return User::create([
			'grade' => 'normal',
			'parent_id' => $this->inviter ? $this->inviter->id : 0,
			'phone' => $this->phoneNumber ?? null,
			'name' => $this->phoneNumber ?? uniqid(time()),
			'password' => $this->password,
			'wm_openid' => $this->openid,
			'wx_unionid' => $this->unionid,
		]);
	}
	
	protected function authOld(User $user)
	{
		$user->update([
			'phone' => $this->phoneNumber ?? null,
			'password' => $this->password,
			'wm_openid' => $this->openid,
			'wx_unionid' => $this->unionid,
		]);
		return $user;
	}
	
	protected function auth()
	{
		$this->loginValidate();
		
		$user = null;
		
		$user = $this->userFromPhone();
		
		if ($user) {
			$this->authOld($user);
		} else {
			$user = $this->authNew();
			event(new NewUserRegisteredEvent($user));
			return $user;
		}
		
		return $user;
	}
	
	public function phone()
	{
		$this->phoneValidate();
		if ($user = $this->userFromPhoneAndOpenId()) {
			return $user;
		}
		return [
			'phone' => $this->phoneNumber,
			'unionid' => $this->unionid,
			'openid' => $this->openid,
		];
	}
	
	public function resetOpenid()
	{
		$this->phoneValidate();
		$user = request()->user();
		$user->update([
			'wm_openid' => $this->openid,
			'wx_unionid' => $this->unionid,
		]);
	}
	
	public function login()
	{
		return $this->auth();
	}
}
